package com.ra.util;

import java.util.BitSet;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * 
 * A、如果想查询附近的点？如何操作 查出该点的gehash值，然后到数据库里面进行前缀匹配就可以了。
 * 
 * B、如果想查询附近点，特定范围内，例如一个点周围500米的点，如何搞？ 可以查询结果，在结果中进行筛选，将geohash进行解码为经纬度，然后进行比较
 * 
 * C、编码长度与精度
 * 9 - 2米
 * 8 - 19米
 * 7 - 76米
 * 6 - 610米
 * 5 - 2.4公里
 * 4 - 20公里
 * 3 - 78公里
 * 2 - 630公里
 * 1 - 2500公里
 * 
 * @see http://iamzhongyong.iteye.com/blog/1399333
 *      http://blog.csdn.net/sunrise_2013/article/details/42395261
 * 
 * @author Jerry
 * 
 */
public class Geohash {

	private static final Log logger = LogFactory.getLog(Geohash.class);
	
	private static int numbits = 6 * 5;
	final static char[] digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8',
			'9', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'm', 'n', 'p',
			'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' };

	final static HashMap<Character, Integer> lookup = new HashMap<Character, Integer>();
	static {
		int i = 0;
		for (char c : digits)
			lookup.put(c, i++);
	}

	private static String BASE32 = "0123456789bcdefghjkmnpqrstuvwxyz";

	private static int RIGHT = 0;
	private static int LEFT = 1;
	private static int TOP = 2;
	private static int BOTTOM = 3;

	private static int EVEN = 0;
	private static int ODD = 1;

	private static String[][] NEIGHBORS;
	private static String[][] BORDERS;

	static {
		NEIGHBORS = new String[4][2];
		BORDERS = new String[4][2];

		NEIGHBORS[BOTTOM][EVEN] = "bc01fg45238967deuvhjyznpkmstqrwx";
		NEIGHBORS[TOP][EVEN] = "238967debc01fg45kmstqrwxuvhjyznp";
		NEIGHBORS[LEFT][EVEN] = "p0r21436x8zb9dcf5h7kjnmqesgutwvy";
		NEIGHBORS[RIGHT][EVEN] = "14365h7k9dcfesgujnmqp0r2twvyx8zb";

		BORDERS[BOTTOM][EVEN] = "bcfguvyz";
		BORDERS[TOP][EVEN] = "0145hjnp";
		BORDERS[LEFT][EVEN] = "prxz";
		BORDERS[RIGHT][EVEN] = "028b";

		NEIGHBORS[BOTTOM][ODD] = NEIGHBORS[LEFT][EVEN];
		NEIGHBORS[TOP][ODD] = NEIGHBORS[RIGHT][EVEN];
		NEIGHBORS[LEFT][ODD] = NEIGHBORS[BOTTOM][EVEN];
		NEIGHBORS[RIGHT][ODD] = NEIGHBORS[TOP][EVEN];

		BORDERS[BOTTOM][ODD] = BORDERS[LEFT][EVEN];
		BORDERS[TOP][ODD] = BORDERS[RIGHT][EVEN];
		BORDERS[LEFT][ODD] = BORDERS[BOTTOM][EVEN];
		BORDERS[RIGHT][ODD] = BORDERS[TOP][EVEN];
	}

	private static String calculateAdjacent(String srcHash, int dir) {
		srcHash = srcHash.toLowerCase();
		char lastChr = srcHash.charAt(srcHash.length() - 1);
		int type = (srcHash.length() % 2) == 1 ? ODD : EVEN;
		String base = srcHash.substring(0, srcHash.length() - 1);
		if (BORDERS[dir][type].indexOf(lastChr) != -1) {
			base = calculateAdjacent(base, dir);
		}
		return base + BASE32.charAt(NEIGHBORS[dir][type].indexOf(lastChr));
	}

	/**
	 * 
	 * 查找给定geohash点的周围8个点
	 * 
	 * @param geohash
	 * @return
	 */
	public static String[] getGeoHashExpand(String geohash) {
		try {
			String geohashTop = calculateAdjacent(geohash, TOP);
			String geohashBottom = calculateAdjacent(geohash, BOTTOM);
			String geohashRight = calculateAdjacent(geohash, RIGHT);
			String geohashLeft = calculateAdjacent(geohash, LEFT);

			String geohashTopLeft = calculateAdjacent(geohashLeft, TOP);
			String geohashTopRight = calculateAdjacent(geohashRight, TOP);
			String geohashBottomRight = calculateAdjacent(geohashRight, BOTTOM);
			String geohashBottomLeft = calculateAdjacent(geohashLeft, BOTTOM);

			String[] expand = { geohash, geohashTop, geohashBottom,
					geohashRight, geohashLeft, geohashTopLeft, geohashTopRight,
					geohashBottomRight, geohashBottomLeft };
			return expand;
		} catch (Exception e) {
			logger.error("GeoHash Error", e);
			return null;
		}
	}
	
	/**
	 * 默认地球半径
	 */
	private static double EARTH_RADIUS = 6378.137;
	 
	/**
	 * 计算经纬度点对应正方形4个点的坐标
	 *
	 * @param longitude
	 * @param latitude
	 * @param distance	单位是公里
	 * @return
	 */
	public static Map<String, double[]> returnLLSquarePoint(double latitude, double longitude, double distance) {
	    Map<String, double[]> squareMap = new HashMap<String, double[]>();
		// 计算经度弧度,从弧度转换为角度
		double dLongitude = 2 * (Math.asin(Math.sin(distance
				/ (2 * EARTH_RADIUS))
				/ Math.cos(Math.toRadians(latitude))));
		dLongitude = Math.toDegrees(dLongitude);
		// 计算纬度角度
		double dLatitude = distance / EARTH_RADIUS;
		dLatitude = Math.toDegrees(dLatitude);
	    // 正方形
	    double[] leftTopPoint = { latitude + dLatitude, longitude - dLongitude };
	    double[] rightTopPoint = { latitude + dLatitude, longitude + dLongitude };
	    double[] leftBottomPoint = { latitude - dLatitude,
	            longitude - dLongitude };
	    double[] rightBottomPoint = { latitude - dLatitude,
	            longitude + dLongitude };
	    squareMap.put("leftTopPoint", leftTopPoint);
	    squareMap.put("rightTopPoint", rightTopPoint);
	    squareMap.put("leftBottomPoint", leftBottomPoint);
	    squareMap.put("rightBottomPoint", rightBottomPoint);
	    return squareMap;
	}

	/**
	 * 
	 * 将geohash还原成经纬度
	 * 
	 * @param geohash
	 * @return
	 */
	public static double[] decode(String geohash) {
		StringBuilder buffer = new StringBuilder();
		for (char c : geohash.toCharArray()) {

			int i = lookup.get(c) + 32;
			buffer.append(Integer.toString(i, 2).substring(1));
		}

		BitSet lonset = new BitSet();
		BitSet latset = new BitSet();

		// even bits
		int j = 0;
		for (int i = 0; i < numbits * 2; i += 2) {
			boolean isSet = false;
			if (i < buffer.length())
				isSet = buffer.charAt(i) == '1';
			lonset.set(j++, isSet);
		}

		// odd bits
		j = 0;
		for (int i = 1; i < numbits * 2; i += 2) {
			boolean isSet = false;
			if (i < buffer.length())
				isSet = buffer.charAt(i) == '1';
			latset.set(j++, isSet);
		}

		double lon = decode(lonset, -180, 180);
		double lat = decode(latset, -90, 90);

		return new double[] { lat, lon };
	}

	private static double decode(BitSet bs, double floor, double ceiling) {
		double mid = 0;
		for (int i = 0; i < bs.length(); i++) {
			mid = (floor + ceiling) / 2;
			if (bs.get(i))
				floor = mid;
			else
				ceiling = mid;
		}
		return mid;
	}

	/**
	 * 
	 * 将经纬度转成geohash值
	 * 
	 * @param lat
	 * @param lon
	 * @return
	 */
	public static String encode(double lat, double lon) {
		BitSet latbits = getBits(lat, -90, 90);
		BitSet lonbits = getBits(lon, -180, 180);
		StringBuilder buffer = new StringBuilder();
		for (int i = 0; i < numbits; i++) {
			buffer.append((lonbits.get(i)) ? '1' : '0');
			buffer.append((latbits.get(i)) ? '1' : '0');
		}
		return base32(Long.parseLong(buffer.toString(), 2));
	}

	private static BitSet getBits(double lat, double floor, double ceiling) {
		BitSet buffer = new BitSet(numbits);
		for (int i = 0; i < numbits; i++) {
			double mid = (floor + ceiling) / 2;
			if (lat >= mid) {
				buffer.set(i);
				floor = mid;
			} else {
				ceiling = mid;
			}
		}
		return buffer;
	}

	public static String base32(long i) {
		char[] buf = new char[65];
		int charPos = 64;
		boolean negative = (i < 0);
		if (!negative)
			i = -i;
		while (i <= -32) {
			buf[charPos--] = digits[(int) (-(i % 32))];
			i /= 32;
		}
		buf[charPos] = digits[(int) (-i)];

		if (negative)
			buf[--charPos] = '-';
		return new String(buf, charPos, (65 - charPos));
	}

	/**
	 * 
	 * 两点计算距离，传入两点的经纬度, 返回以米为单位的距离
	 * 
	 */
	public static double getPointDistance(double lat1, double lng1,
			double lat2, double lng2) {
		double result = 0;

		double radLat1 = radian(lat1);

		double ratlat2 = radian(lat2);
		double a = radian(lat1) - radian(lat2);
		double b = radian(lng1) - radian(lng2);

		result = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2)
				+ Math.cos(radLat1) * Math.cos(ratlat2)
				* Math.pow(Math.sin(b / 2), 2)));
		result = result * EARTH_RADIUS;

		result = Math.round(result * 1000); // 返回的单位是米，四舍五入

		return result;
	}

	/** 由角度转换为弧度 */
	private static double radian(double d) {
		return (d * Math.PI) / 180.00;
	}

}